C++ Language Extensions Added by ZK The c++ language is extended by zk to add a few minor features, such as new kinds of macros, which peacefully coexist with the standard "#define" macros, and which make it easier to implement a command shell without asking the user to type excess characters at the command line. For example, to implement a typical command line command such as "copy *.txt c:\txt" using normal c++ macros would require excessive syntax such as "copy(*.txt,c:\\txt)", but by using the new kinds of macros provided by zk, it can be implemented such that the command the user types at the zk command line looks exactly the same as when typed at the operating system command line. There are two new kinds of macros, #m and #m1. They work similarly to #define, but with different syntax and features. #define is unchanged. #m macroname(arguments) macro definition % The % has to be the last character on the last line of the macro. You don't need backslashes at the ends of lines as you do in #define macros. So you always need a % at the end of every #m macro, even if it's just one line, e.g. #m tmac(arg1,arg2) definition text % The formal arguments are the same as for #define, except that you can also use an asterisk as the last formal argument, for the vararg feature. The actual arguments can be the same as for a #define macro, except that the opening parenthesis has to touch the macro name, with no white space between them. But instead of that you can use use a new syntax, which is improved and preferred, in which the arguments follow the macro name with no parentheses and are separated by white space. For example, if the definition is #m abc(x) x x % then the call can be abc(123) or abc 123 but not abc (123) because that would result in (123) (123) instead of 123 123. See some of the examples later in this document (extend.txt) to clarify this. The vararg feature uses an asterisk as the rightmost formal argument, and invokes that argument from within the macro by using the name vararg, or varargfirst or varargrest. The cmds macro, shown later in this document, is an example to clarify this. When the name vararg appears in a macro that uses this feature, it is replaced with all the text of all the arguments that take the place of the asterisk in the formals. The other two, varargfirst and varargrest, are for parsing that text, giving the first and rest of those arguments. Recursion, as used in the cmds macro, makes such parsing work for indefinite lists. When a macro uses the vararg feature and is called using the whitespace syntax (i.e., no parentheses), the actual argument list needs to be terminated to prevent it from extending to the end of the file. Such termination is provided by a semicolon, which is consumed by the macro. Thus if you need a semicolon at the end of a statement implemented by such a macro, you should put it in the macro definition to avoid having to put two semicolons at the end of the macro call. This only applies to macros that use the vararg feature. #m1 is the same as #m except that the macro call has to be at the start of a C++ statement or declaration. That reduces the probability that the macro name will conflict with names of variables, etc. It's useful for defining commands to use at the command line. The command line has an implied semicolon at the end, but it only applies in contexts where appropriate. That is, only when you are typing in commands for immediate execution and a semicolon is expected; Otherwise the command continues to the next line. If you don't want an implied semicolon at that point, even though a semicolon is expected, you can continue the command to the next line explicitly with a backslash. Try it yourself to understand which contexts are affected. The whole purpose of this feature is to reduce your typing when typing simple commands, so it will be like an ordinary command shell for that purpose. Here are some example macros, discussed further below: #m1 sys(*) xsystem(#vararg); nop(); % #m def1cmd(cmd) #m1 cmd(*) sys cmd vararg; %% % #m1 cmds(*) #if #varargfirst[0] def1cmd varargfirst cmds varargrest; #endif % The sys macro use the vararg feature and calls a function named xsystem, which is an interface to the compiler vendor's system function, which executes an operating system command. The nop() function does nothing, and its only purpose is to cause the last function on the command line to return void (to return nothing) because some types of return values are displayed automatically by zk, such as when you type in an expression and want to see the result. The nop() causes the return value of xsystem to not be displayed. It's the return value of system, and is normally not very useful in this context, and would confuse the output. The def1cmd macro is used by the cmds macro to define one command. The double %% is how a nested macro definition is terminated, to not confuse it with the single % terminating the outer macro. The cmds macro calls itself recursively, once per argument. See the discussion earlier in this document for an explanation of the vararg feature. The #varargfirst[0] stringizes the first argument and takes its first character, and if that's 0, it means the stringize result is empty, which means there is no first argument, which means the whole argument list has been processed by the recursion. In general, you can copy the style of the cmds macro to make other recursive macros that work the same way, even if you don't understand some of the more subtle points explained here. Don't get confused by the stringizing. The # attached to the start of the names vararg and varargfirst causes their results to be stringized, which means to enclose them in quotation marks and do any processing needed to make them legal string literals. And don't get confused by the #varargfirst[0], which translates to ""[0] when the argument list is exhausted. ""[0] is 0, because strings are terminated by a 0, and an empty string is terminated at [0], and that's how the cmds macro knows when to stop processing arguments. Another added preprocessor feature is #H, which is a like #include, but only works with zk header files, *.zh, and which has slightly neater syntax. If you have three header files named one.zh, two,zh, and three.zh, the normal #include syntax is three lines: #include "one.zh" #include "two.zh" #include "three.zh" But the #H syntax is just one line: #H one two three (with the .zh extensions omitted, because #H adds them internally.) Also, using the #H feature, even just once in your program, turns on a flag which causes all header files used by your program, even those using #include, to only be processed once, regardless of how many times they are referenced. This is normally desirable, because processing the same header file more than once is normally a waste of time, even if it has protection causing it to be skipped after the first time, because just reading it and skipping through it takes time, even if not very much. But in general the #H feature is not very significant and you can safely ignore it, and use #include instead.